package org.easier.framework.elasticsearch.hightlevel.impl;

import org.easier.framework.elasticsearch.*;
import org.easier.framework.elasticsearch.page.OrderByInput;
import org.easier.framework.elasticsearch.page.PageContent;
import org.easier.framework.elasticsearch.page.SortFun;
import org.easier.framework.elasticsearch.searchmodel.*;
import org.easier.framework.json.JsonAgent;
import org.elasticsearch.action.DocWriteResponse;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.action.support.replication.ReplicationResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.client.indices.GetIndexResponse;
import org.elasticsearch.common.lucene.search.function.CombineFunction;
import org.elasticsearch.common.lucene.search.function.FieldValueFactorFunction;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.*;
import org.elasticsearch.index.query.functionscore.FieldValueFactorFunctionBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;

import java.io.IOException;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

public class EsHightlevel7Client implements SearchAdapter {
    private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    private RestHighLevelClient client;

    public EsHightlevel7Client(RestHighLevelClient restHighLevelClient) {
        this.client = restHighLevelClient;
    }


    @Override
    public void close() {
        try {
            client.close();
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }

    /**
     * 创建索引
     *
     * @param name   索引名
     * @param fields 字段列表
     * @return
     */
    @Override
    public boolean createIndex(String name, List<CreateIndexField> fields) {
        try {
            CreateIndexRequest request = new CreateIndexRequest(name);
            XContentBuilder builder = XContentFactory.jsonBuilder();
            builder.startObject();
            builder.startObject("properties");
            for (CreateIndexField item : fields) {
                builderField(builder, item);
            }
            builder.endObject();
            builder.endObject();
            request.mapping(builder);

            CreateIndexResponse createIndexResponse = client.indices().create(request, RequestOptions.DEFAULT);
            return createIndexResponse.isAcknowledged();
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }

    /**
     * 递归构建字段
     *
     * @param builder
     * @param item
     * @throws IOException
     */
    private void builderField(XContentBuilder builder, CreateIndexField item) throws IOException {
        builder.startObject(item.getName());
        builder.field("type", item.getType().getName());
        if (item.getAnalyzer() != null) {
            builder.field("analyzer", item.getAnalyzer().getName());
            builder.field("search_analyzer", item.getAnalyzer().getName());
        }
        builder.field("index", item.isIndex());
        if (item.isSort()) {
            builder.field("fielddata", true);
        }
        if (item.getFormat() != null && !"".equals(item.getFormat())) {
            builder.field("format", item.getFormat());
        }

        if (item.getFields() != null && item.getFields().size() > 0) {
            builder.startObject("fields");
            for (CreateIndexField field : item.getFields()) {
                builderField(builder, field);
            }
            builder.endObject();
        }

        builder.endObject();
    }


    /**
     * 存在索引
     *
     * @param name 索引名
     * @return
     */
    @Override
    public boolean existsIndex(String name) {
        try {
            GetIndexRequest request = new GetIndexRequest(name);
            request.local(false);
            request.humanReadable(true);
            request.includeDefaults(false);
            return client.indices().exists(request, RequestOptions.DEFAULT);
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }

    /**
     * 删除索引
     *
     * @param name 索引名
     * @return
     */
    @Override
    public boolean deleteIndex(String name) {
        try {
            DeleteIndexRequest request = new DeleteIndexRequest(name);
            AcknowledgedResponse deleteIndexResponse = client.indices().delete(request, RequestOptions.DEFAULT);
            return deleteIndexResponse.isAcknowledged();
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }

    /**
     * 返回索引
     *
     * @param name 索引名
     * @return
     */
    @Override
    public Map<String, Object> getIndex(String name) {
        try {
            GetIndexRequest request = new GetIndexRequest(name);
            request.local(false);
            request.humanReadable(true);
            request.includeDefaults(false);
            GetIndexResponse getIndexResponse = client.indices().get(request, RequestOptions.DEFAULT);
            return getIndexResponse.getMappings().get(name).getSourceAsMap();
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }

    /**
     * 添加数据
     *
     * @param indexName 索引名
     * @param id        索引id
     * @param content   内容
     * @return
     */
    @Override
    public boolean addData(String indexName, long id, String content) {
        try {
            IndexRequest request = new IndexRequest(indexName);
            request.id(String.valueOf(id));
            request.source(content, XContentType.JSON);
            IndexResponse indexResponse = client.index(request, RequestOptions.DEFAULT);
            ReplicationResponse.ShardInfo shardInfo = indexResponse.getShardInfo();
            return shardInfo.getTotal() != shardInfo.getSuccessful();
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }


    /**
     * 添加数据
     *
     * @param indexName 索引名
     * @param id        索引id
     * @param content   内容
     * @return
     */
    @Override
    public boolean addData(String indexName, long id, Object content) {
        return addData(indexName, id, JsonAgent.serialize(content));
    }

    /**
     * 添加数据
     *
     * @param indexName 索引名
     * @param id        索引id
     * @param content   内容
     * @return
     */
    @Override
    public boolean addData(String indexName, long id, Map<String, Object> content) {
        try {
            IndexRequest request = new IndexRequest(indexName);
            request.id(String.valueOf(id));
            request.source(content);
            IndexResponse indexResponse = client.index(request, RequestOptions.DEFAULT);
            ReplicationResponse.ShardInfo shardInfo = indexResponse.getShardInfo();
            return shardInfo.getTotal() != shardInfo.getSuccessful();
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }


    /**
     * 返回数据
     *
     * @param indexName 索引名
     * @param id        索引id
     * @param clazz     返回类型
     * @return
     */
    @Override
    public <T> T getData(String indexName, long id, Class<T> clazz) {
        try {
            GetRequest getRequest = new GetRequest(indexName, String.valueOf(id));
            GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT);
            if (!getResponse.isExists()) {
                return null;
            }
            return JsonAgent.toObject(getResponse.toString(), "_source", clazz);
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }

    /**
     * 返回数据
     *
     * @param indexName 索引名
     * @param id        索引id
     * @return
     */
    @Override
    public Map<String, Object> getData(String indexName, long id) {
        try {
            GetRequest getRequest = new GetRequest(indexName, String.valueOf(id));
            GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT);
            if (!getResponse.isExists()) {
                return null;
            }
            return getResponse.getSource();
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }

    /**
     * 存在数据
     *
     * @param indexName 索引名
     * @param id        索引id
     * @return
     */
    @Override
    public boolean existsData(String indexName, long id) {
        try {
            GetRequest getRequest = new GetRequest(indexName, String.valueOf(id));
            getRequest.fetchSourceContext(new FetchSourceContext(false));
            getRequest.storedFields("_none_");
            return client.exists(getRequest, RequestOptions.DEFAULT);
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }

    /**
     * 删除数据
     *
     * @param indexName 索引名
     * @param id        索引id
     * @return
     */
    @Override
    public boolean deleteData(String indexName, long id) {
        try {
            DeleteRequest request = new DeleteRequest(indexName, "_doc", String.valueOf(id));
            DeleteResponse result = client.delete(request, RequestOptions.DEFAULT);
            return result.getResult() == DocWriteResponse.Result.DELETED;
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }

    /**
     * 更新数据
     *
     * @param indexName 索引名
     * @param id        索引id
     * @param content   数据
     * @return
     */
    @Override
    public boolean updateData(String indexName, long id, String content) {
        return updateData(indexName, id, content, 3);
    }

    /**
     * 更新数据
     *
     * @param indexName 索引名
     * @param id        索引id
     * @param content   数据
     * @return
     */
    @Override
    public boolean updateData(String indexName, long id, Object content) {
        return updateData(indexName, id, content, 3);
    }

    /**
     * 更新数据
     *
     * @param indexName   索引名
     * @param id          索引id
     * @param content     数据
     * @param retryNumber 重试次数
     * @return
     */
    @Override
    public boolean updateData(String indexName, long id, Object content, int retryNumber) {
        return updateData(indexName, id, JsonAgent.serialize(content), retryNumber);


    }

    /**
     * 更新数据
     *
     * @param indexName   索引名
     * @param id          索引id
     * @param content     数据
     * @param retryNumber 重试次数
     * @return
     */
    @Override
    public boolean updateData(String indexName, long id, String content, int retryNumber) {
        try {
            UpdateRequest request = new UpdateRequest(indexName, "_doc", String.valueOf(id));
            request.doc(content, XContentType.JSON);
            request.retryOnConflict(retryNumber);
            UpdateResponse updateResponse = client.update(request, RequestOptions.DEFAULT);
            ReplicationResponse.ShardInfo shardInfo = updateResponse.getShardInfo();
            return shardInfo.getTotal() != shardInfo.getSuccessful();
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }

    /**
     * 更新数据
     *
     * @param indexName 索引名
     * @param id        索引id
     * @param content   数据
     * @return
     */
    @Override
    public boolean updateData(String indexName, long id, Map<String, Object> content) {
        return updateData(indexName, id, content, 3);
    }


    /**
     * 更新数据
     *
     * @param indexName   索引名
     * @param id          索引id
     * @param content     数据
     * @param retryNumber 重试次数
     * @return
     */
    @Override
    public boolean updateData(String indexName, long id, Map<String, Object> content, int retryNumber) {
        try {
            UpdateRequest request = new UpdateRequest(indexName, "_doc", String.valueOf(id));
            request.doc(content);
            request.retryOnConflict(retryNumber);
            UpdateResponse updateResponse = client.update(request, RequestOptions.DEFAULT);
            ReplicationResponse.ShardInfo shardInfo = updateResponse.getShardInfo();
            return shardInfo.getTotal() != shardInfo.getSuccessful();
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }

    /**
     * 搜索，取前10条，超时时间1秒
     *
     * @param indexName 索引名
     * @param key       键名
     * @param value     值
     * @param clazz     返回类型
     * @param <T>
     * @return
     */
    @Override
    public <T> PageContent<T> search(String indexName, String key, String value, Class<T> clazz) {
        return search(indexName, key, value, 1, 10, 1000, clazz);
    }

    /**
     * 搜索，取前10条，超时时间1秒
     *
     * @param indexName 索引名
     * @param key       键名
     * @param value     值
     * @return
     */
    @Override
    public Map<String, Object> search(String indexName, String key, String value) {
        return search(indexName, key, value, 1, 10, 1000);
    }

    /**
     * 搜索，取前N条，超时时间1秒
     *
     * @param indexName 索引名
     * @param key       键名
     * @param value     值
     * @param size      返回条数
     * @param clazz     返回类型
     * @param <T>
     * @return
     */
    @Override
    public <T> PageContent<T> search(String indexName, String key, String value, int size, Class<T> clazz) {
        return search(indexName, key, value, 1, size, 1000, clazz);
    }

    /**
     * 搜索，取前N条，超时时间1秒
     *
     * @param indexName 索引名
     * @param key       键名
     * @param value     值
     * @param size      返回条数
     * @return
     */
    @Override
    public Map<String, Object> search(String indexName, String key, String value, int size) {
        return search(indexName, key, value, 1, size, 1000);
    }

    /**
     * 搜索，超时时间1秒
     *
     * @param indexName 索引名
     * @param key       键名
     * @param value     值
     * @param number    第几页
     * @param size      取多少条
     * @param clazz     返回类型
     * @param <T>
     * @return
     */
    @Override
    public <T> PageContent<T> search(String indexName, String key, String value, int number, int size, Class<T> clazz) {
        return search(indexName, key, value, number, size, 1000, clazz);
    }

    /**
     * 搜索，超时时间1秒
     *
     * @param indexName 索引名
     * @param key       键名
     * @param value     值
     * @param number    第几页
     * @param size      取多少条
     * @return
     */
    @Override
    public Map<String, Object> search(String indexName, String key, String value, int number, int size) {
        return search(indexName, key, value, number, size, 1000);
    }


    /**
     * 按条件搜索前10条，超时时间一秒
     *
     * @param indexName 索引名
     * @param search    返回类型
     * @param clazz     返回类型
     * @param <T>
     * @return
     */
    @Override
    public <T> PageContent<T> search(String indexName, BoolSearch search, Class<T> clazz) {
        return search(indexName, search, null, 1, 10, 1000, clazz);
    }

    /**
     * 按条件搜索前10条，超时时间一秒
     *
     * @param indexName 索引名
     * @param search    返回类型
     * @return
     */
    @Override
    public Map<String, Object> search(String indexName, BoolSearch search) {
        return search(indexName, search, null, 1, 10, 1000);
    }

    /**
     * 按条件搜索前N条，超时时间一秒
     *
     * @param indexName 索引名
     * @param search    返回类型
     * @param size      取多少条
     * @param clazz     返回类型
     * @param <T>
     * @return
     */
    @Override
    public <T> PageContent<T> search(String indexName, BoolSearch search, int size, Class<T> clazz) {
        return search(indexName, search, null, 1, size, 1000, clazz);
    }

    /**
     * 按条件搜索前N条，超时时间一秒
     *
     * @param indexName 索引名
     * @param search    返回类型
     * @param size      取多少条
     * @return
     */
    @Override
    public Map<String, Object> search(String indexName, BoolSearch search, int size) {
        return search(indexName, search, null, 1, size, 1000);
    }

    /**
     * 按条件搜索，超时时间一秒
     *
     * @param indexName 索引名
     * @param search    搜索条件
     * @param number    第几页
     * @param size      取多少条
     * @param clazz     返回类型
     * @param <T>
     * @return
     */
    @Override
    public <T> PageContent<T> search(String indexName, BoolSearch search, int number, int size, Class<T> clazz) {
        return search(indexName, search, null, number, size, 1000, clazz);
    }

    /**
     * 按条件搜索，超时时间一秒
     *
     * @param indexName 索引名
     * @param search    搜索条件
     * @param number    第几页
     * @param size      取多少条
     * @return
     */
    @Override
    public Map<String, Object> search(String indexName, BoolSearch search, int number, int size) {
        return search(indexName, search, null, number, size, 1000);
    }

    /**
     * 按条件搜索
     *
     * @param indexName    索引名
     * @param search       搜索条件
     * @param number       第几页
     * @param size         取多少条
     * @param milliSeconds 超时时间(豪秒)
     * @param clazz        返回类型
     * @param <T>
     * @return
     */
    @Override
    public <T> PageContent<T> search(String indexName, BoolSearch search, List<OrderByInput> sorts, int number, int size, int milliSeconds, Class<T> clazz) {
        return search(indexName, search, sorts, null, number, size, milliSeconds, clazz);
    }


    /**
     * @param indexName    索引名
     * @param key          键名
     * @param value        值
     * @param number       第几页
     * @param size         取多少条
     * @param milliSeconds 超时时间(豪秒)
     * @param clazz        返回类型
     * @param <T>
     * @return
     */
    @Override
    public <T> PageContent<T> search(String indexName, String key, String value, int number, int size, int milliSeconds, Class<T> clazz) {
        SearchRequest searchRequest = new SearchRequest(indexName);
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        sourceBuilder.from((number - 1) * size);
        sourceBuilder.size(size);
        sourceBuilder.timeout(new TimeValue(milliSeconds, TimeUnit.MILLISECONDS));
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery(key, value);
        boolQueryBuilder.must(matchQueryBuilder);
        sourceBuilder.query(boolQueryBuilder);
        searchRequest.source(sourceBuilder);
        SearchResponse searchResponse = null;
        try {
            searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }

        List<T> items = new ArrayList<>();
        boolean isBaseData = BaseData.class.isAssignableFrom(clazz);
        for (SearchHit item : searchResponse.getHits().getHits()) {
            T object = JsonAgent.toObject(item.toString(), "_source", clazz);
            if (isBaseData) {
                BaseData baseData = (BaseData) object;
                baseData.setScore(JsonAgent.toObject(item.toString(), "_score", float.class));
                baseData.setId(JsonAgent.toObject(item.toString(), "_id", long.class));
            }
            items.add(object);
        }

        return new PageContent<>(items, searchResponse.getHits().getTotalHits().value);
    }

    @Override
    public Map<String, Object> search(String indexName, BoolSearch search, List<OrderByInput> sorts, SortFun sortFun, int number, int size, int milliSeconds) {
        try {
            SearchRequest searchRequest = new SearchRequest(indexName);
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            sourceBuilder.from((number - 1) * size);
            sourceBuilder.size(size);
            sourceBuilder.timeout(new TimeValue(milliSeconds, TimeUnit.MILLISECONDS));
            sourceBuilder.query(getQueryBuilder(search, sortFun));
            List<SortBuilder<?>> sortBuilders = getSort(sorts);
            for (SortBuilder item : sortBuilders) {
                sourceBuilder.sort(item);
            }

            searchRequest.source(sourceBuilder);
            SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

            Map<String, Object> result = new HashMap<String, Object>();
            for (SearchHit item : searchResponse.getHits().getHits()) {
                result.putAll(item.getSourceAsMap());
            }
            return result;
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }


    /**
     * 按条件搜索
     *
     * @param indexName    索引名
     * @param search       搜索条件
     * @param number       第几页
     * @param size         取多少条
     * @param milliSeconds 超时时间(豪秒)
     * @param clazz        返回类型
     * @param <T>
     * @return
     */
    @Override
    public <T> PageContent<T> search(String indexName, BoolSearch search, List<OrderByInput> sorts, SortFun sortFun, int number, int size, int milliSeconds, Class<T> clazz) {
        try {
            SearchRequest searchRequest = new SearchRequest(indexName);
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            sourceBuilder.from((number - 1) * size);
            sourceBuilder.size(size);
            sourceBuilder.timeout(new TimeValue(milliSeconds, TimeUnit.MILLISECONDS));
            sourceBuilder.trackTotalHits(true);
            sourceBuilder.query(getQueryBuilder(search, sortFun));
            List<SortBuilder<?>> sortBuilders = getSort(sorts);
            for (SortBuilder item : sortBuilders) {
                sourceBuilder.sort(item);
            }
            searchRequest.source(sourceBuilder);

            SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

            List<T> items = new ArrayList<>();
            boolean isBaseData = BaseData.class.isAssignableFrom(clazz);
            for (SearchHit item : searchResponse.getHits().getHits()) {
                T object = JsonAgent.toObject(item.toString(), "_source", clazz);
                if (isBaseData) {
                    BaseData baseData = (BaseData) object;
                    baseData.setScore(JsonAgent.toObject(item.toString(), "_score", float.class));
                    baseData.setId(JsonAgent.toObject(item.toString(), "_id", long.class));
                }
            }
            return new PageContent<>(items, searchResponse.getHits().getTotalHits().value);
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }

    /**
     * 按条件搜索
     *
     * @param indexName    索引名
     * @param search       搜索条件
     * @param number       第几页
     * @param size         取多少条
     * @param milliSeconds 超时时间(豪秒)
     * @return
     */
    @Override
    public Map<String, Object> search(String indexName, BoolSearch search, List<OrderByInput> sorts, int number, int size, int milliSeconds) {
        return search(indexName, search, sorts, null, number, size, milliSeconds);
    }


    /**
     * 按条件搜索
     *
     * @param indexName    索引名
     * @param number       第几页
     * @param size         取多少条
     * @param milliSeconds 超时时间(豪秒)
     * @return
     */
    @Override
    public Map<String, Object> search(String indexName, String key, String value, int number, int size, int milliSeconds) {
        try {
            SearchRequest searchRequest = new SearchRequest(indexName);
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            sourceBuilder.from((number - 1) * size);
            sourceBuilder.size(size);
            sourceBuilder.timeout(new TimeValue(milliSeconds, TimeUnit.MILLISECONDS));

            searchRequest.source(sourceBuilder);
            SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

            Map<String, Object> result = new HashMap<String, Object>();
            for (SearchHit item : searchResponse.getHits().getHits()) {
                result.putAll(item.getSourceAsMap());
            }
            return result;
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }

    private List<SortBuilder<?>> getSort(List<OrderByInput> sorts) {
        if (sorts == null || sorts.size() == 0) {
            return new ArrayList<>();
        }
        List<SortBuilder<?>> result = new ArrayList<>();
        for (OrderByInput sort : sorts) {
            FieldSortBuilder order = SortBuilders.fieldSort(sort.getPropertyName()).order(sort.isDescending() ? SortOrder.DESC : SortOrder.ASC).unmappedType("long");

            result.add(order);
        }
        return result;
    }


    private QueryBuilder getQueryBuilder(Search search, SortFun sortFun) {
        if (sortFun == null) {
            return getQueryBuilder(search, OperatorTypes.AND);
        }

        FieldValueFactorFunctionBuilder fieldQuery = new FieldValueFactorFunctionBuilder(
                sortFun.getKey());
        fieldQuery.factor(sortFun.getFactor());
        fieldQuery.modifier(FieldValueFactorFunction.Modifier.LOG1P);

        return QueryBuilders.functionScoreQuery(getQueryBuilder(search, OperatorTypes.AND), fieldQuery).boostMode(CombineFunction.SUM);
    }

    private BoolQueryBuilder getQueryBuilder(Search search, OperatorTypes operatorType) {
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        if (search.getClass() != BoolSearch.class) {
            return boolQueryBuilder;
        }

        BoolSearch boolSearch = (BoolSearch) search;
        for (Search item : boolSearch.getSearches()) {
            if (item instanceof TextSearch) {
                TextSearch textSearch = (TextSearch) item;
                MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery(textSearch.getKey(), textSearch.getValue());
                setOperator(boolQueryBuilder, operatorType, matchQueryBuilder);
                if (textSearch.getBoost() != 0) {
                    matchQueryBuilder.boost(textSearch.getBoost());
                }
            }

            if (item instanceof KeyWordSearch) {
                KeyWordSearch keyWordSearch = (KeyWordSearch) item;
                TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery(keyWordSearch.getKey(), keyWordSearch.getValue());
                if (operatorType != OperatorTypes.OR) {
                    boolQueryBuilder.filter(termQueryBuilder);
                } else {
                    setOperator(boolQueryBuilder, operatorType, QueryBuilders.boolQuery().filter(termQueryBuilder));
                }
            }

            if (item instanceof KeyWordsSearch) {
                KeyWordsSearch keyWordsSearch = (KeyWordsSearch) item;
                TermsQueryBuilder termsQueryBuilder = QueryBuilders.termsQuery(keyWordsSearch.getKey(), keyWordsSearch.getValue());
                if (operatorType != OperatorTypes.OR) {
                    boolQueryBuilder.filter(termsQueryBuilder);
                } else {
                    setOperator(boolQueryBuilder, operatorType, QueryBuilders.boolQuery().filter(termsQueryBuilder));
                }
            }

            if (item instanceof RangeSearch) {
                RangeSearch range = (RangeSearch) item;
                RangeQueryBuilder builder = QueryBuilders.rangeQuery(range.getKey());
                if (range.getGtValue() != null) {
                    builder.gt(range.getGtValue());
                }
                if (range.getGteValue() != null) {
                    builder.gte(range.getGteValue());
                }
                if (range.getLtValue() != null) {
                    builder.lt(range.getLtValue());
                }
                if (range.getLteValue() != null) {
                    builder.lte(range.getLteValue());
                }
                if (operatorType != OperatorTypes.OR) {
                    boolQueryBuilder.filter(builder);
                } else {
                    setOperator(boolQueryBuilder, operatorType, QueryBuilders.boolQuery().filter(builder));
                }
            }
            if (item instanceof DateRangeSearch) {
                DateRangeSearch dateRangeSearch = (DateRangeSearch) item;
                RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery(dateRangeSearch.getKey());
                if (dateRangeSearch.getGtValue() != null) {
                    rangeQueryBuilder.gt(formatter.format(dateRangeSearch.getGtValue()));
                }
                if (dateRangeSearch.getGteValue() != null) {
                    rangeQueryBuilder.gt(formatter.format(dateRangeSearch.getGteValue()));
                }
                if (dateRangeSearch.getLtValue() != null) {
                    rangeQueryBuilder.gt(formatter.format(dateRangeSearch.getLtValue()));
                }
                if (dateRangeSearch.getLteValue() != null) {
                    rangeQueryBuilder.gt(formatter.format(dateRangeSearch.getLteValue()));
                }
                rangeQueryBuilder.timeZone(dateRangeSearch.getTimeZone());
                if (operatorType != OperatorTypes.OR) {
                    boolQueryBuilder.filter(rangeQueryBuilder);
                } else {
                    setOperator(boolQueryBuilder, operatorType, QueryBuilders.boolQuery().filter(rangeQueryBuilder));
                }
            }
            if (item instanceof NumberSearch) {
                NumberSearch numberSearch = (NumberSearch) item;
                TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery(numberSearch.getKey(), numberSearch.getValue());
                if (operatorType != OperatorTypes.OR) {
                    boolQueryBuilder.filter(termQueryBuilder);
                } else {
                    setOperator(boolQueryBuilder, operatorType, QueryBuilders.boolQuery().filter(termQueryBuilder));
                }
            }

            if (item instanceof BoolSearch) {
                setOperator(boolQueryBuilder, operatorType, getQueryBuilder(item, ((BoolSearch) item).getOperatorType()));
            }
        }
        return boolQueryBuilder;
    }

    private void setOperator(BoolQueryBuilder boolQueryBuilder, OperatorTypes types, QueryBuilder queryBuilder) {
        if (types == OperatorTypes.AND) {
            boolQueryBuilder.must(queryBuilder);
        } else if (types == OperatorTypes.OR) {
            boolQueryBuilder.should(queryBuilder);
        } else if (types == OperatorTypes.NOT) {
            boolQueryBuilder.mustNot(queryBuilder);
        }
    }
}
